unit fposclientcore;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, fposUtils, fpstructs, lNet, Sqlite3DS, fpg_main, fpg_dialogs,
  tcpbuffers, md5, fpg_base;

type
  TmyTimer = class(TfpgTimer)
  public
    Counter: integer;
  end;


  { TfposClient }

  TfposClient = class(TObject)
  private
    fConnection: TTCPConnection;
    fServer: string;
    fPort: word;
    fSalenumber: integer;
    fDataRevision: integer;
    fQuit: Boolean;
    fOnConnect: TNotifyEvent;
    fOnDisconnect: TNotifyEvent;
    fConfigDir: string;
    fTimeoutLimit: TDateTime;
    fTimeout: TDateTime;   //value in milliseconds
    millisecond: TDateTime;
    procedure fConnectionDisconnect ( aSocket: TLSocket ) ;
    procedure fConnectionConnect ( aSocket: TLSocket ) ;
    procedure fConnectionError ( const msg: string; aSocket: TLSocket ) ;
    procedure ProcessIncomingMessage ( Sender: TObject );
    procedure CheckSockets(Sender: TObject);
    procedure CheckServer(Sender: TObject);
  public
    fTerminalID: integer;
    TerminalName: string;
    SalesFile: string;
    constructor Create(aOwner: TComponent);
    destructor Destroy; override;
     property ConfigDir: string read fConfigDir write fConfigDir;
     function RegisterTerminal(TermName, ServerAddress: string; ServerPort: word): Boolean;
    function SendTerminalID( aSocket: TLSocket ): integer;
    function LogonToServer(ServerAddress: string; ServerPort: word): Boolean;
    procedure CheckConnection;
    procedure SendSaleDetails(Sender: TObject; SQL: string);
    procedure SendShiftStatus( StaffID: integer; ShiftStatus: TfpMessage; SQL: string );
    property OnConnect: TNotifyEvent read fOnConnect write fOnConnect;
    property OnDisConnect: TNotifyEvent read fOnDisconnect write fOnDisconnect;
    property Connection: TTCPConnection read fConnection;
  end;

var
  fposClient: TfposClient;

implementation

uses fposclientdb;

{ TfposClient }

procedure TfposClient.fConnectionDisconnect(aSocket: TLSocket);
begin
  {$IFDEF LINUX}
  writeln('disconnected  ');
  writeln('Trying to reconnect...  ');
  {$ENDIF}
  if Assigned(fOnDisconnect) then fOnDisconnect(aSocket);
end;

procedure TfposClient.fConnectionConnect(aSocket: TLSocket);
begin
  {$IFDEF LINUX}
  WriteLn('Connected to ', aSocket.PeerAddress);
  {$ENDIF}
  if fTerminalID>0
     then SendTerminalID(nil);
  if Assigned(fOnConnect) then fOnConnect(aSocket);
end;

procedure TfposClient.fConnectionError(const msg: string; aSocket: TLSocket);
begin
  {$IFDEF LINUX}
  writeln(msg);
  {$ENDIF}
end;

procedure TfposClient.ProcessIncomingMessage ( Sender: TObject ) ;
var
  Databuffer: String;
  startsalenum: integer;
  startshifttime: TJulianDateTime;
  SQL: String;
  sendbuf: TTCPSendBuffer;
  SalesUpdate: TSqlite3Dataset;
begin
  with Sender as TTCPBuffer do
      begin
         {$IFDEF DEBUG} writeln('Processing message');  {$ENDIF}
         case header.MessageType of
              fpmSubmitConfigData:
                 case header.datatype of
                      fpdTerminalID: begin
                                       Data.Read(fTerminalID, SizeOf(fTerminalID));
                                       {$IFDEF DEBUG}
                                       writeln('Received TerminalID: ', fTerminalID);
                                       {$ENDIF}
                                     end;
                      fpdSQL,
                      fpdSQLUpdate:  begin
                                       SetLength(Databuffer, header.datalength);
                                       Data.Read(Databuffer[1], header.datalength);
                                       ClientDB.UpdateConfig(Databuffer);
                                     end;
                 end;
              fpmRequestSalesData:
                      if Data.Read(startsalenum, SizeOf(startsalenum)) > 0 then
                        begin
                          SQL := ClientDB.GetAsSQLInsert('SaleSummary', 'SELECT * FROM Salesummary WHERE saleID>' + IntToStr(startsalenum))
                                 + ClientDB.GetAsSQLInsert('SaleDetails', 'SELECT * FROM SaleDetails WHERE saleID>' + IntToStr(startsalenum));
                          SalesUpdate := ClientDB.GetTable('SaleDetails', 'select distinct itemid, SUM(quantity) AS sold from saledetails WHERE saleid>'
                                      + IntToStr(startsalenum) +' group by itemid;');
                          while not SalesUpdate.EOF do
                                begin
                                  SQL := SQL + 'Update Products SET stocklevel=stocklevel-' + SalesUpdate.Fields[1].AsString
                                             + ' WHERE productID=' + SalesUpdate.Fields[0].AsString + ' AND suppliercode>-1;';
                                  SalesUpdate.Next;
                                end;
                          if Length(SQL)>0 then
                            begin
                              sendbuf := TTCPSendBuffer.Create(nil, Socket);
                              sendbuf.Header := NewHeader(ClientDB.TerminalID, fpmRegisterSale);
                              sendbuf.AddData(SQL);
                              Connection.SendMessage(sendbuf);
                            end;     // TODO: check for errors and data inconsistency
                        end;
              fpmRequestShiftData:
                      if Data.Read(startshifttime, SizeOf(startshifttime)) > 0 then
                        begin
                          {$IFDEF LINUX} writeln('Shift Data Requested'); {$ENDIF}
                          SQL := ClientDB.GetAsSQLInsert('Shifts', 'SELECT * FROM Shifts WHERE starttime>' + FloatToStr(startshifttime));
                          if Length(SQL)>0 then
                            begin
                              SQL := 'DELETE FROM Shifts WHERE terminalID=' + IntToStr( ClientDB.TerminalID )
                                     + ' AND starttime>' + FloatToStr(startshifttime) + ';' + LineEnding
                                     + SQL;
                              {$IFDEF DEBUG} writeln(SQL); {$ENDIF}
                              sendbuf := TTCPSendBuffer.Create(nil, Socket);
                              sendbuf.Header := NewHeader(ClientDB.TerminalID, fpmRegisterSale);
                              sendbuf.AddData(SQL);
                              Connection.SendMessage(sendbuf);
                              Connection.CallAction;
                            end;        // TODO: check for errors and data inconsistency
                        end;
              fpmTimecheck:
                        if Data.Read(startshifttime, SizeOf(startshifttime)) > 0 then
                          begin
                            TimeAdjustment := startshifttime-Now;
                            {$IFDEF DEBUG} writeln ( 'TimeAdjustment=', FormatFloat('0.#####', TimeAdjustment)); {$ENDIF}
                          end;
         end;
     end;
  fConnection.DeleteMessage(TTCPBuffer(Sender));
end;

procedure TfposClient.CheckSockets(Sender: TObject);
begin
 // writeln('CheckSockets');
  fpgApplication.ProcessMessages;
end;

procedure TfposClient.CheckServer(Sender: TObject);
begin
  if not Connection.Connected then
    begin
     fConnection.Connect(fServer, fPort);
     writeln('trying to connect...');
    end;
  fpgApplication.ProcessMessages;
  fConnection.CallAction;
end;

function TfposClient.SendTerminalID(aSocket: TLSocket): integer;
var
  dataStream: TTCPSendBuffer;
  lastshiftstart: TJulianDateTime;
begin
  {$IFDEF DEBUG}
  WriteLn('Send Terminal ID: ' + IntToStr( fTerminalID ));
  {$ENDIF}
  dataStream := TTCPSendBuffer.Create(nil, nil);
  dataStream.Header := NewHeader(fTerminalID, fpmTerminalLogon);
  fSalenumber := ClientDB.SaleRef;
  dataStream.AddData(fSalenumber);
  lastshiftstart := ClientDB.ShiftInfo.StartTime;
  dataStream.AddData(lastshiftstart);
  {$IFDEF DEBUG}
  WriteLn('Send Terminal Datarevision: ' + IntToStr( ClientDB.DataRevision ));
  {$ENDIF}
  dataStream.AddData(ClientDB.DataRevision);
  fConnection.SendMessage(dataStream);
end;

function TfposClient.LogonToServer ( ServerAddress: string; ServerPort: word
  ) : Boolean;
begin
  {$IFDEF DEBUG}
  writeln('Logon');
  {$ENDIF}
  fServer := ServerAddress;
  fPort := ServerPort;
  fTerminalID := ClientDB.TerminalID;
  Result := false;
  if not fConnection.Connected
     then fConnection.Connect(ServerAddress, ServerPort);
  fTimeoutLimit := Now + fTimeout;
  while  (not fConnection.Connected) and (Now<fTimeoutLimit) do
           fConnection.CallAction;
  if fConnection.Connected then
     begin
       // Result := SendTerminalID(nil)=0;
       Result := true;
     end
  else
     begin
        {$IFDEF LINUX} writeln('Error logging on to server: Unable to connect') {$ENDIF} ;
        fConnectionDisconnect(nil);
     end;
end;

procedure TfposClient.CheckConnection;
begin
  CheckServer(nil);
end;

procedure TfposClient.SendSaleDetails ( Sender: TObject; SQL: string ) ;
var
  fpHeader: tfpHeader;
  buf: TTCPSendBuffer;
begin
  if not fConnection.Connection.Connected then exit;
  {$IFDEF LINUX} writeln('Send RegisterSale'); {$ENDIF}
  fpHeader := NewHeader(ClientDB.TerminalID, fpmRegisterSale);
  fpHeader.MessageType := fpmRegisterSale;
  buf := TTCPSendBuffer.Create(nil, nil);
  buf.Header := fpHeader;
  buf.AddData(SQL);
  fConnection.SendMessage(buf);
end;

procedure TfposClient.SendShiftStatus(StaffID: integer;
  ShiftStatus: TfpMessage; SQL: string);
var
  buf: TTCPSendBuffer;
  header: tfpHeader;
begin
  if not fConnection.Connected then exit;
  SQL := IntToStr(StaffID) + #3 + SQL;
  buf := TTCPSendBuffer.Create(nil, nil);
  Header := NewHeader(ClientDB.TerminalID, ShiftStatus);
  buf.Header := Header;
  buf.AddData(SQL);
  fConnection.SendMessage(buf);
  {$IFDEF LINUX} writeln('Sent ShiftChange '); {$ENDIF}
end;

constructor TfposClient.Create(aOwner: TComponent);
begin
  fTerminalID := -1;
  millisecond := 1/(24 * 60 * 60 * 1000);
  fConnection := TTCPConnection.Create;
  fConnection.Connection.Timeout := 0;
  fConnection.Connection.ReuseAddress := true;
  fConnection.OnReceiveMessage := @ProcessIncomingMessage;
  //fConnection.OnReceive := @fConnectionReceive;
  //fConnection.OnError := @fConnectionError;
  fConnection.OnDisconnect := @fConnectionDisconnect;
  fConnection.OnConnect := @fConnectionConnect;
  fConfigDir := GetFirePOSConfigDir;
  fTimeout := DEFAULT_TIMEOUT * millisecond;
end;

destructor TfposClient.Destroy;
begin
  fConnection.Free;
  inherited Destroy;
end;

function TfposClient.RegisterTerminal ( TermName, ServerAddress: string;
  ServerPort: word ) : Boolean;
var
  buffer: TTCPSendBuffer;
  ScreenWidth: LongInt;
  ScreenHeight: LongInt;
begin
  Result := false;
  buffer := TTCPSendBuffer.Create(nil, nil);
  buffer.Header := NewHeader(-1, fpmRegisterTerminal);
  buffer.AddData(TermName);
  ScreenWidth := fpgApplication.ScreenWidth;
  ScreenHeight := fpgApplication.ScreenHeight;
  buffer.AddData(fpgApplication.ScreenWidth);
  buffer.AddData(fpgApplication.ScreenHeight);
    {$IFDEF DEBUG} writeln('Registering Termname: ', TermName); {$ENDIF}
  fTimeoutLimit := Now + fTimeout;
  try
    if not fConnection.Connected
       then fConnection.Connect(ServerAddress, ServerPort);
    fTimeoutLimit := Now + fTimeout;
    while  (not fConnection.Connected) and (Now<fTimeoutLimit) do
           fConnection.CallAction;
    if fConnection.Connected then
      begin
        fConnection.SendMessage(buffer);
        {$IFDEF DEBUG} writeln('Sent register request'); {$ENDIF}
//        StartTimer(fTimeout);
        fTimeoutLimit := Now + fTimeout;
        while (fTerminalID=-1) and (Now<fTimeoutLimit) do
              fConnection.CallAction;
        if fTerminalID=-1 then ShowMessage('No response from server: please check settings', 'Error');
      end
    else ShowMessage('Could not connect to server: please check settings', 'Error');
    Result := fTerminalID>0;
  except
    if buffer <> nil
       then buffer.Free;
  end;
  if Result then
    begin
      ClientDB.UpdateConfig('UPDATE Terminal SET TerminalID=' + IntToStr(fTerminalID)
                   + ', name=''' + TermName + ''', '
                   + 'serveraddress="' + ServerAddress + '", '
                   + 'serverport=' + IntToStr(ServerPort) + ' WHERE TerminalID=-1;'); //TODO: record correct datarevision number
      ClientDB.TerminalID := fTerminalID;
    end
  else
    begin
      fConnection.Disconnect;
      fTimeoutLimit := Now + fTimeout;
      while (fTerminalID=-1) and (Now<fTimeoutLimit) do ;
      if fConnection.Connected then fConnection.Disconnect(true);
    end;
end;

end.

